package service;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import model.JoinLeague;
import model.League;
import model.PlayerStanding;
import model.User;
import exceptions.DivisionByZeroException;
import exceptions.UnknownVariableException;

/**
 * Class which implements the operations with the player standing.
 * @author Andre Melo
 * @author Sergio Danilo
 * @author Marcus Leite
 *
 */
public class PlayerStandingServiceImpl implements PlayerStandingService {
	
	/*
	 * The Entity Manager, responsible for the operations with the database.
	 */
	@PersistenceContext
	private EntityManager em;
	
	/*
	 * The instance of LeagueService.
	 */
	private LeagueService leagueService;
	
	/*
	 * The instance of MatchService.
	 */
	private MatchService matchService;
	
	/*
	 * A list of expression elements.
	 */
	private List<Character> expressionElements = new ArrayList<Character>();
	
	@Override
	public void defineStandingsExpression(Integer leagueId, String expression) throws ScriptException, DivisionByZeroException, UnknownVariableException {
		League league = leagueService.findById(leagueId);
		verifySyntaxErrorInExpression(expression);
		league.setStandingExpression(expression);
	}

	/*
	 * Method that verifies if the expression has any syntax error.
	 */
	private void verifySyntaxErrorInExpression(String expression) throws ScriptException, UnknownVariableException {
		String from1 = "seasonWins";
		String from2 = "seasonLosses";
		Integer to = 1;
		Integer pointer = 0;
		verifyUnknownVariable(expression, pointer);
		int start1 = expression.indexOf(from1);
		int lf = from1.length();
		char [] targetChars1 = expression.toCharArray();
		StringBuffer buffer1 = new StringBuffer();
		int copyFrom = 0;
		while (start1 != -1) {
			buffer1.append (targetChars1, copyFrom, start1-copyFrom);
		    buffer1.append (to);
		    copyFrom = start1 + lf;
		    start1 = expression.indexOf (from1, copyFrom);
		}
		expression = buffer1.append (targetChars1, copyFrom, targetChars1.length - copyFrom).toString();
		int start2 = expression.indexOf(from2);
		int lf2 = from2.length();
		char [] targetChars2 = expression.toCharArray();
		StringBuffer buffer2 = new StringBuffer();
		int copyFrom2 = 0;
		while (start2 != -1) {
		    buffer2.append (targetChars2, copyFrom2, start2-copyFrom2);
		    buffer2.append (to);
		    copyFrom2 = start2 + lf2;
		    start2 = expression.indexOf (from2, copyFrom2);
		}
		expression = buffer2.append (targetChars2, copyFrom2, targetChars2.length - copyFrom2).toString();
		ScriptEngineManager manager = new ScriptEngineManager();  
		ScriptEngine engine = manager.getEngineByName("JavaScript");  
		@SuppressWarnings("unused")
		Object obj = null;
		try {  
			obj = engine.eval(expression);
		} catch (ScriptException exc) {
			throw new ScriptException("Syntax error in standings expression");
		}  
	}

	/*
	 * Method that takes a player from a league and call the method that calculate the standing.
	 */
	private void verifyExpressionAndCalculatePlayersStandings(Integer leagueId, String expression) throws ScriptException, DivisionByZeroException, UnknownVariableException {
		List<League> league = leagueService.getAllLeagues();
		for (League leagueIt : league) {
			List<JoinLeague> joinLeague = leagueIt.getJoinLeagues();
			for (JoinLeague join : joinLeague) {
				User user = join.getUser();
				if (!user.isOperator()) {
					PlayerStanding ps = new PlayerStanding();
					ps.setLeagueId(leagueIt.getId());
					ps.setUserId(user.getId());
					Integer standing = calculate(leagueId, user.getId(), expression);
					ps.setStanding(standing);
					validateNewEntry(user);
					save(ps);
				}
			}	
		}
	}
	
	/*
	 * Method that delete an outdated standing.
	 */
	@SuppressWarnings("unchecked")
	private void validateNewEntry(User user) {
		Query query = em.createQuery("Select p From PlayerStanding p");
		List<PlayerStanding> psList = query.getResultList();
		for (PlayerStanding ps : psList) {
			if (ps.getUserId().equals(user.getId()))
				delete(ps);
		}
	}

	/*
	 * Method that calculate the standing of a player.
	 */
	private Integer calculate(Integer leagueId, Integer userId, String expression) throws ScriptException, DivisionByZeroException, UnknownVariableException {
		String seasonWins = matchService.getNumberOfWins(userId, leagueId).toString();
		String seasonLosses = matchService.getNumberOfLosses(userId, leagueId).toString();
		String from1 = "seasonWins";
		String from2 = "seasonLosses";
		Integer pointer = 0;
		verifyUnknownVariable(expression, pointer);
		int start1 = expression.indexOf(from1);
		int lf = from1.length();
		char [] targetChars1 = expression.toCharArray();
		StringBuffer buffer1 = new StringBuffer();
		int copyFrom = 0;
		while (start1 != -1) {
			buffer1.append (targetChars1, copyFrom, start1-copyFrom);
		    buffer1.append (seasonWins);
		    copyFrom = start1 + lf;
		    start1 = expression.indexOf (from1, copyFrom);
		}
		expression = buffer1.append (targetChars1, copyFrom, targetChars1.length - copyFrom).toString();
		int start2 = expression.indexOf(from2);
		int lf2 = from2.length();
		char [] targetChars2 = expression.toCharArray();
		StringBuffer buffer2 = new StringBuffer();
		int copyFrom2 = 0;
		while (start2 != -1) {
		    buffer2.append (targetChars2, copyFrom2, start2-copyFrom2);
		    buffer2.append (seasonLosses);
		    copyFrom2 = start2 + lf2;
		    start2 = expression.indexOf (from2, copyFrom2);
		}
		expression = buffer2.append (targetChars2, copyFrom2, targetChars2.length - copyFrom2).toString();
		ScriptEngineManager manager = new ScriptEngineManager();  
		ScriptEngine engine = manager.getEngineByName("JavaScript");  
		Object obj = null;
		try {  
			obj = engine.eval(expression);
		} catch (ScriptException exc) {
			throw new ScriptException("Syntax error in standings expression");
		}  
		Integer standing = ((Double)obj).intValue();
		if (standing.equals(Integer.MAX_VALUE))
			throw new DivisionByZeroException("Division by zero in standings expression");
		return standing;
	}
	
	/*
	 * Method that verifies if an unknown variable exists in the given expression. 
	 */
	private void verifyUnknownVariable(String expression, Integer pointer) throws UnknownVariableException {
		performExpressionElements();
		String word = "";
		while (pointer < expression.length()) {
			if (!expressionElements.contains(expression.charAt(pointer)))
				word += expression.charAt(pointer);
			else break;
			pointer++;
		}
		boolean decreased = false;
		if (pointer >= expression.length()) {
			decreased = true;
			pointer--;
		}
		if (!word.equals("seasonWins") && !word.equals("seasonLosses") &&
				!expressionElements.contains(expression.charAt(pointer))) {
			clearExpressionElements();
			throw new UnknownVariableException("Unknown variable in standings expression");
		}
		if (decreased) pointer++;
		clearExpressionElements();
		pointer++;
		if (pointer < expression.length())
			verifyUnknownVariable(expression, pointer);
	}

	/*
	 * Method that clear the list of expression elements.
	 */
	private void clearExpressionElements() {
		expressionElements.clear();
	}

	@Override
	public void save(PlayerStanding ps) {
		em.persist(ps);		
	}
	
	@Override
	public void update(PlayerStanding ps) {
		em.merge(ps);
	}

	@SuppressWarnings("unchecked")
	@Override
	public Object getPlayerStanding(Integer playerId, Integer leagueId) throws ScriptException, DivisionByZeroException, UnknownVariableException {
		League league = leagueService.findById(leagueId);
		verifyExpressionAndCalculatePlayersStandings(leagueId, league.getStandingExpression());
		Query query = em.createQuery("Select p From PlayerStanding p");
		List<PlayerStanding> psList = query.getResultList();
		for (PlayerStanding ps : psList) {
			if (ps.getUserId().equals(playerId) && ps.getLeagueId().equals(leagueId))
				return ps.getStanding();
		}
		return null;
	}

	/**
	 * @return the leagueService
	 */
	@Override
	public LeagueService getLeagueService() {
		return leagueService;
	}

	@Override
	public void setLeagueService(LeagueService leagueService) {
		this.leagueService = leagueService;
	}

	@Override
	public MatchService getMatchService() {
		return matchService;
	}

	@Override
	public void setMatchService(MatchService matchService) {
		this.matchService = matchService;
	}

	@Override
	public void delete(PlayerStanding ps) {
		em.remove(ps);
	}

	@SuppressWarnings("unchecked")
	@Override
	public void removeAllStandings() {
		Query query = em.createQuery("Select p From PlayerStanding p");
		List<PlayerStanding> psList = query.getResultList();
		for (PlayerStanding ps : psList) {
			delete(ps);
		}
	}
	
	/*
	 * Perform the list of expression elements.
	 */
	private void performExpressionElements() {
		expressionElements.add('+');
		expressionElements.add('-');
		expressionElements.add('*');
		expressionElements.add('/');
		expressionElements.add('(');
		expressionElements.add(')');
		expressionElements.add(')');
		expressionElements.add('0');
		expressionElements.add('1');
		expressionElements.add('2');
		expressionElements.add('3');
		expressionElements.add('4');
		expressionElements.add('5');
		expressionElements.add('6');
		expressionElements.add('7');
		expressionElements.add('8');
		expressionElements.add('9');
		expressionElements.add(' ');
	}
	
}
